package br.ufpe.cin.camid.core.lifecycle;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import br.ufpe.cin.camid.core.events.Core;
import br.ufpe.cin.camid.core.lifecycle.factory.ManagedObjectFactory;
import br.ufpe.cin.camid.core.objects.ManagedObject;
import br.ufpe.cin.camid.core.objects.ManagedObjectHandler;
import br.ufpe.cin.camid.core.objects.ManagedObjectType;

import com.google.common.eventbus.EventBus;
import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
public class ObjectManagerFacade 
{
	
	private StatelessObjectManager statelessManager;
	
	private StatefulObjectManager statefulManager;
	
	private Map<String, ManagedObjectLease> leaseMap;
	
	private EventBus eventBus;
	
	private Lock readerLock;
	
	private Lock writerLock;

	@Inject
	public ObjectManagerFacade(StatelessObjectManager statelessManager,
			StatefulObjectManager statefulManager, ManagedObjectFactory factory, @Core EventBus eventBus) 
	{
		super();
		this.statelessManager = statelessManager;
		this.statefulManager = statefulManager;
		this.leaseMap = new HashMap<String, ManagedObjectLease> ();
		this.eventBus = eventBus;
		ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
		readerLock = readWriteLock.readLock();
		writerLock = readWriteLock.writeLock();
	}
	
	public ManagedObjectHandler create(String objectId, Class<?> objectDefinition)
	{
		
		ManagedObjectHandler handler = null;
		
		ManagedObject metadata = objectDefinition.getAnnotation(ManagedObject.class);
		ManagedObjectType type = metadata.type();
		ObjectManager manager = getHandlerFromTypeAndId(type);
		
		try 
		{	
			writerLock.lock();
			handler = manager.create(objectId, objectDefinition);
			
			ManagedObjectLease lease = createLeaseFromManagedObject(objectId, metadata);
			leaseMap.put(objectId, lease);
		}
		finally
		{
			writerLock.unlock();
		}
		
		eventBus.post(new ManagedObjectEvent(ManagedObjectEventType.CREATION, objectId));
		return handler;
	}
	
	private ManagedObjectLease createLeaseFromManagedObject(String objectId, ManagedObject metadata)
	{
		ManagedObjectType type = metadata.type();
		Long deactivationTime = metadata.deactivationTime();
		
		ManagedObjectLease lease = new ManagedObjectLease(objectId, type, deactivationTime);
		
		return lease;
	}

	public ManagedObjectHandler find(String objectId) 
	{
		ManagedObjectHandler handler = null;
		try
		{
			readerLock.lock();
			ManagedObjectLease lease = leaseMap.get(objectId);
			if(lease != null)
			{
				ManagedObjectType type = lease.getType();
				
				ObjectManager manager = getHandlerFromTypeAndId(type);
				handler = manager.find(objectId);
				leaseMap.put(objectId, lease.renewLease());
			}
		}
		finally
		{
			readerLock.unlock();
		}
		
		return handler;
	}

	public ManagedObjectHandler insert(String objectId,
			Object object) 
	{	
		ManagedObjectHandler oldHandler = null;
		
		ManagedObject metadata = object.getClass().getAnnotation(ManagedObject.class);
		ManagedObjectType type = metadata.type();
		
		ObjectManager manager = getHandlerFromTypeAndId(type);
		try
		{
			writerLock.lock();
			oldHandler = manager.insert(objectId, object);
			ManagedObjectLease lease = createLeaseFromManagedObject(objectId, metadata);
			leaseMap.put(objectId, lease);
		}
		finally
		{
			writerLock.unlock();
		}
		eventBus.post(new ManagedObjectEvent(ManagedObjectEventType.INSERTION, objectId));
		return oldHandler;
	}

	public ManagedObjectHandler remove(String objectId) 
	{
		ManagedObjectHandler handler = null;
		try 
		{
			writerLock.lock();
			ManagedObjectLease lease = leaseMap.get(objectId);
			ManagedObjectType type = lease.getType();
			
			ObjectManager manager = getHandlerFromTypeAndId(type);
			handler = manager.remove(objectId);
		}
		finally
		{
			writerLock.unlock();	
		}
		eventBus.post(new ManagedObjectEvent(ManagedObjectEventType.REMOVAL, objectId));
		return handler;
	}
	
	private ObjectManager getHandlerFromTypeAndId(ManagedObjectType type)
	{
		ObjectManager manager = null;
		if(type.equals(ManagedObjectType.STATEFUL))
		{
			manager = statefulManager;
		}
		else if(type.equals(ManagedObjectType.STATELESS))
		{
			manager = statelessManager;
		}
		return manager;
	}

	public List<ManagedObjectLease> getManagedObjectLeases() {
		List<ManagedObjectLease> leases = new ArrayList<ManagedObjectLease> ();
		
		try
		{
			readerLock.lock();
			for(Entry<String,ManagedObjectLease> entry : this.leaseMap.entrySet())
			{
				ManagedObjectLease lease = entry.getValue();
				leases.add(lease);
			}
		}
		finally
		{
			readerLock.unlock();
		}
		
		return leases;
	}
	
}
